package com.abel.bigwater.dataserver.model.qdies

import com.abel.bigwater.data.BcdString
import com.abel.bigwater.dataserver.model.PacketCmdException
import io.netty.buffer.ByteBuf
import java.util.*

class D10Packet {
    companion object {
        const val MIN_LEN = 1 + 4 + 1 + 1 + 1 + 1
        const val IESD10_HEAD = "IESD10"
        val DUMMY_FILL = byteArrayOf(0xFE.toByte(), 0xFE.toByte())
    }

    /**
     * 远传地址
     */
    var remote: String? = null

    /**
     * udp/tcp
     */
    var stream: String? = null

    val head = 0x68

    var includeLogin = false

    var address: BcdString? = null

    var func: FuncType? = null

    /**
     * 1 byte
     */
    var dataLen: Int? = null

    var data: D10BaseData? = null

    var crc: Byte? = null

    val tail = 0x16

    var text: String? = null

    var totalLen: Int? = null

    var index: Int? = null

    fun fill(buf: ByteBuf) {
        when (func) {
            FuncType.Heart -> assert(data is D10HeartData)
            FuncType.Login -> assert(data is D10LoginData)
            FuncType.AskState -> assert(data is DtenAskStateData)

            FuncType.AskTime -> assert(data is DtenAskTimeData)
            FuncType.SetTime -> assert(data is DtenSetTimeData)

            FuncType.AskIpPort -> assert(data is DtenAskIpPortData)
            FuncType.SetIpPort -> assert(data is DtenSetIpPortData)

            FuncType.SetPulseBase -> assert(data is DtenPulseBaseData)

            FuncType.SetActiveOnline -> assert(data is DtenSetActiveOnlineData)
            FuncType.AskActiveOnline -> assert(data is DtenAskActiveOnlineData)

            FuncType.SetUploadMode -> assert(data is DtenSetUploadModeData)
            FuncType.AskUploadMode -> assert(data is DtenAskUploadModeData)

            FuncType.AskMeterConfig -> assert(data is DtenAskMeterConfigData)
            FuncType.Ask485Config -> assert(data is DtenAsk485ConfigData)
            FuncType.AskAnalogConfig -> assert(data is DtenAskAnalogConfigData)
            FuncType.AskPulseConfig -> assert(data is DtenAskPulseConfigData)

            FuncType.Ask485Hist -> assert(data is DtenAsk485HistData)
            FuncType.AskAnalogHist -> assert(data is DtenAskAnalogHistData)
            FuncType.AskPulseHist -> assert(data is DtenAskPulseHistData)

            FuncType.Ask485Now -> assert(data is DtenAsk485NowData)
            FuncType.AskAnalogNow -> assert(data is DtenAskAnalogNowData)
            FuncType.AskPulseNow -> assert(data is DtenAskPulseNowData)

            else -> throw PacketCmdException("unknown data: ${data}")
        }

        buf.writeBytes(buf.alloc().buffer().apply {
            writeByte(head)

            writeBytes(address!!.hexArray)
            writeByte(func!!.code.toInt())

            dataLen = data!!.count()
            writeByte(dataLen!!.toInt())
            data!!.fill(this)

            val tmp = this.duplicate()
            crc = (0x100 - ByteArray(tmp.readableBytes()).also { tmp.readBytes(it) }.sum()).and(0xFF).toByte()
            writeByte(crc!!.toInt())
            writeByte(tail)
        })
    }

    fun parse(buf: ByteBuf): Boolean {
        if (buf.readableBytes() < MIN_LEN) return false

        // remove header
        var consumed = 0
        buf.duplicate().apply {
            val h1 = ByteArray(IESD10_HEAD.length) { 0 }.also {
                duplicate().readBytes(it)
            }
            if (Arrays.equals(h1, IESD10_HEAD.toByteArray())) {
                // address
                skipBytes(IESD10_HEAD.length + 4)

                consumed += IESD10_HEAD.length + 4

                includeLogin = true
            }

            var h2 = ByteArray(2) { 0 }
            while (readableBytes() >= 2) {
                readBytes(h2)
                if (Arrays.equals(DUMMY_FILL, h2)) {
                    consumed += 2
                } else {
                    return@apply
                }
            }
        }

        // parse
        buf.duplicate().apply {
            skipBytes(consumed)
            if (readableBytes() < MIN_LEN) {
                return false
            }

            if (head != readByte().toInt()) return false

            address = BcdString.create(this, 4)

            val cmd = readByte()
            func = FuncType.findByCode(cmd)
            if (func == null) throw PacketCmdException("unsupported command: ${cmd.toString(16)}")

            dataLen = readUnsignedByte().toInt()

            // not enough bytes
            if (readableBytes() < dataLen!! + 2) {
                return false
            }
            val dataBuf = readBytes(dataLen!!)
            data = when (func) {
                FuncType.Login -> {
                    D10LoginData().also { if (!it.parse(dataBuf)) return false }
                }
                FuncType.Heart -> {
                    D10HeartData().also { if (!it.parse(dataBuf)) return false }
                }
                FuncType.AskState -> {
                    DtenAskStateData().also { if (!it.parse(dataBuf)) return false }
                }

                FuncType.SetTime -> {
                    DtenSetTimeData(dataLen!!).also { if (!it.parse(dataBuf)) return false }
                }
                FuncType.AskTime -> {
                    DtenAskTimeData(dataLen!!).also { if (!it.parse(dataBuf)) return false }
                }

                FuncType.AskIpPort -> {
                    DtenAskIpPortData(dataLen!!).also { if (!it.parse(dataBuf)) return false }
                }
                FuncType.SetIpPort -> {
                    DtenSetIpPortData(dataLen!!).also { if (!it.parse(dataBuf)) return false }
                }

                FuncType.SetPulseBase -> {
                    DtenPulseBaseData(dataLen!!).also { if (!it.parse(dataBuf)) return false }
                }

                FuncType.SetActiveOnline -> {
                    DtenSetActiveOnlineData(dataLen!!)
                }
                FuncType.AskActiveOnline -> {
                    DtenAskActiveOnlineData(dataLen!!).also { if (!it.parse(dataBuf)) return false }
                }

                FuncType.SetUploadMode -> {
                    DtenSetUploadModeData()
                }
                FuncType.AskUploadMode -> {
                    DtenAskUploadModeData(dataLen!!).also { if (!it.parse(dataBuf)) return false }
                }

                FuncType.AskMeterConfig -> {
                    DtenAskMeterConfigData(dataLen!!).also { if (!it.parse(dataBuf)) return false }
                }
                FuncType.Ask485Config -> {
                    DtenAsk485ConfigData(dataLen!!).also { if (!it.parse(dataBuf)) return false }
                }
                FuncType.AskAnalogConfig -> {
                    DtenAskAnalogConfigData(dataLen!!).also { if (!it.parse(dataBuf)) return false }
                }
                FuncType.AskPulseConfig -> {
                    DtenAskPulseConfigData(dataLen!!).also { if (!it.parse(dataBuf)) return false }
                }

                FuncType.Ask485Hist -> {
                    DtenAsk485HistData(dataLen!!).also { if (!it.parse(dataBuf)) return false }
                }
                FuncType.AskAnalogHist -> {
                    DtenAskAnalogHistData(dataLen!!).also { if (!it.parse(dataBuf)) return false }
                }
                FuncType.AskPulseHist -> {
                    DtenAskPulseHistData(dataLen!!).also { if (!it.parse(dataBuf)) return false }
                }

                FuncType.AskWarn -> {
                    DtenAskWarnData().also { if (!it.parse(dataBuf)) return false }
                }
                FuncType.ClearWarn -> {
                    DtenClearWarnData().also { if (!it.parse(dataBuf)) return false }
                }

                FuncType.Ask485Now -> {
                    DtenAsk485NowData(dataLen!!).also { if (!it.parse(dataBuf)) return false }
                }
                FuncType.AskAnalogNow -> {
                    DtenAskAnalogNowData().also { if (!it.parse(dataBuf)) return false }
                }
                FuncType.AskPulseNow -> {
                    DtenAskPulseNowData().also { if (!it.parse(dataBuf)) return false }
                }

                else -> {
                    null
                }
            }

            crc = readByte()

            if (tail != readByte().toInt()) return false

            if (includeLogin && readableBytes() >= 2) {
                index = readUnsignedShort()
            }
        }

        // consume the bytes
        text = BcdString.hexString(buf, consumed + MIN_LEN + dataLen!!, false)
        totalLen = consumed + MIN_LEN + dataLen!! + (if (index != null) 2 else 0)
        buf.skipBytes( totalLen!!)

        return true
    }
}

enum class FuncType(val code: Byte) {
    /**
     * 设定D10时间
    上位机发送：帧头 地址 0x01 0x07 年 月 日 时 分 秒 星期 校验 帧尾
    时间格式正确时，D10应答：帧头 地址 0x01 0x01 0xAA 校验 帧尾
    时间格式错误时，D10应答：帧头 地址 0x01 0x01 0xCC 校验 帧尾
    说明：年月日时分秒星期使用BCD码，以下同。
     */
    SetTime(0x01),

    /**
     * 读取D10当前时间
    上位机发送：帧头 地址 0x02 0x00 校验 帧尾
    D10应答：帧头 地址 0x02 0x07 年 月 日 时 分 秒 星期 校验 帧尾
     */
    AskTime(0x02),

    AskRssi(0x03),

    SetIpPort(0x06),

    AskIpPort(0x07),

    /**
     * 0x10 表计配置
    上位机发送： 帧头 地址 0x10 0x04 485表计配置 模拟量表计配置
    脉冲表计配置 扩展表计配置 校验 帧尾
    配置成功， D10应答： 帧头 地址 0x10 0x01 0xAA 校验 帧尾
    配置失败， 所启用的485表计的总线地址和规约号重复
    D10应答： 帧头 地址 0x10 0x01 0xE0 校验 帧尾
    配置失败， 所启用的485表计的电压不一致
    D10应答： 帧头 地址 0x10 0x01 0xE1 校验 帧尾
    485表计配置—字节低5位代表5只485表计， 当启用某只485表计时，
    相应的位置1
     */
    SetMeterConfig(0x10),

    /**
     * 读取表计配置
    上位机发送： 帧头 地址 0x11 0x00 校验 帧尾
    D10应答： 帧头 地址 0x11 0x04 485表计配置 模拟量表计配置 脉
    冲表计配置 扩展表计配置 校验 帧尾
     */
    AskMeterConfig(0x11),

    /**
     * 485表计参数设置
    上位机发送： 帧头 地址 0x12 0x15 内部表计地址 485总线地址
    规约号 波特率 数据位 停止位 校验位 模式选择 上传时刻 抄表
    时间间隔 上传时间间隔（抄表N次后上传（N<=24） ） 供电电压 校
    验 帧尾
    参数正常时， D10应答： 帧头 地址 0x12 0x02 内部表计地址 0xAA
    校验 帧尾
    参数异常时， D10应答： 帧头 地址 0x12 0x02 内部表计地址 0xCC
    校验 帧尾
    内部表计地址 1字节
    485总线地址 8字节 不足补零
    01补为01 00 00 00 00 00 00 00
    规约号 1字节 1： 485 大表 188 规约（默认）
    2： 485 大表 645 规约
    3： 485 大表 MODBUS 规约
    4： 天线 1.3
    5： 苍南
    6： 新科
    7： 鸿鹄卡表 1
    8： 鸿鹄卡表 2
    9： 鸿鹄卡表 3
    波特率 1字节 1:2400（默认）
    2:4800
    3:9600
    4:19200
    数据位 1字节 1:8 （默认）
    2:7
    3:6
    停止位 1字节 1:1 （默认）
    2:2
    校验位 1字节 1： 偶校验
    2： 奇校验
    3： 无校验 （默认）
    模式选择 1字节 1： 天模式（默认）
    2： 周模式
    3： 自定义模式
    上传时刻 2字节 两个字节分别代表星期， 小时
    的BCD码
    抄表时间间隔 2字节 单位： 分钟， 取值范围
    0x0001—0xffff
    上传时间间隔（抄表
    N次后上传）
    1字节 取值范围1—24
    供电电压 1字节 单位:V， 取值范围5--24
    说明： 485表计内部地址范围1-5
     */
    Config485(0x12),

    Ask485Config(0x13),

    ConfigAnalog(0x14),

    AskAnalogConfig(0x15),

    /**
     * 脉冲表计模式设置
    上位机发送： 帧头 地址 0x16 0x07 内部表计地址 模式选择 上
    传时刻 抄表时间间隔 上传时间间隔（ 抄表N次后上传（ N<=24） ）
    校验 帧尾
    参数正常时， D10应答： 帧头 地址 0x16 0x02 表计地址 0xAA 校
    验 帧尾
    参数异常时， D10应答： 帧头 地址 0x16 0x02 表计地址 0xCC 校
    验 帧尾
    内部表计地址 1字节
    模式选择 1字节 1： 天模式（ 默认）
    2： 周模式
    3： 自定义模式
    上传时刻 2字节 两 个 字 节 分 别 代 表 星
    期， 小时的BCD码
    抄表时间间隔 2字节 单位： 分钟， 取值范围
    0x0001—0xffff
    上传时间间隔（ 抄表N
    次后上传）
    1字节 取值范围1—24
    说明： 脉冲表计内部地址范围11-14
     */
    ConfigPulse(0x16),

    /**
     * 读取脉冲表计模式设置
    上位机发送： 帧头 地址 0x17 0x01 内部表计地址 校验 帧尾
    D10应答： 帧头 地址 0x17 0x07 内部表计地址 模式选择 上传时
    刻 抄表时间间隔 上传时间间隔（ 抄表N次后上传（ N<=24） ） 校
    验 帧尾
     */
    AskPulseConfig(0x17),

    Ask485Now(0x18),

    AskAnalogNow(0x19),

    AskPulseNow(0x1A),

    Ask485Hist(0x1B),

    AskAnalogHist(0x1C),

    AskPulseHist(0x1D),

    AskWarn(0x1E),

    ClearWarn(0x1F),

    AskState(0x23),

    /**
     * 登陆帧
    D10发送：IESD10+D10地址（总共10字节）
    主站应答：iesd10
     */
    Login(0x24),

    /**
     * 心跳帧
    D10发送：IESD10+D10地址（总共10字节）
    主站应答：iesd10
     */
    Heart(0x25),

    /**
     * 心跳帧间隔
    上位机发送： 帧头 地址 0x26 0x01 间隔时间 校验 帧尾
    D10应答： 帧头 地址 0x26 0x00 校验 帧尾
     */
    SetHeart(0x26),

    /**
     * 配置D10与主站的上传模式
    上位机发送： 帧头 地址 0x27 0x01 D10与主站的上传模式 校验 帧尾 D1
    0应答： 帧头 地址 0x27 0x00 校验 帧尾
    D10与主站的上传模式 1： 短信
    2： GPRS
     */
    SetUploadMode(0x27),

    SetBackIport(0x2A),

    AskBackIport(0x2B),

    /**
     * 脉冲表计参数设置
    上位机发送： 帧头 地址 0x2C 0x08 内部表计地址 表计类型
    脉冲表初值 脉冲常数 表计整数位数 校验 帧尾
    下初值成功， D10应答： 帧头 地址 0x2C 0x02 表计地址 0xAA
    校验 帧尾
    下初值失败， D10应答： 帧头 地址 0x2C 0x02 表计地址 0xCC
    校验 帧尾
    内部表计地址 1字节
    表计类型 1字节 1： 带正反转的双脉冲
    2： 单脉冲
    3： 双干簧管脉冲
    脉冲表初值 4字节 用float值表示,4字节
    脉冲常数 1字节 1： 0.001m3
    2： 0.01 m3
    3： 0.1 m3
    4: 1 m3
    表计整数位数 1字节 5
    6 7 8
    说明： 脉冲表计内部地址范围11-14
     */
    SetPulseBase(0x2C),

    Set485Base(0x36),

    /**
     * 读取D10与主站的上传模式
    上位机发送： 帧头 地址 0x3A 0x00 校验 帧尾
    D10应答： 帧头 地址 0x3A 0x01 D10与主站的上传模式 校验 帧尾
    D10与主站的上传模式 1： 短信
    2： GPRS
     */
    AskUploadMode(0x37),

    /**
     * D10主动上线参数设置
    上位机发送： 帧头 地址 0x38 0x03 上线时刻个数 上线时刻1 上
    线时刻2 校验 帧尾
    D10应答： 帧头 地址 0x38 0x00 校验 帧尾
    上线时刻个数可设 0 1 2， 即最多设置两个时刻
    D10在选择GPRS模式时， 每天在上线时刻登陆GPRS， 查询主站是否
    有命令下发， 无命令， 则继续休眠
     */
    SetActiveOnline(0x38),

    /**
     * D10主动上线参数设置
    上位机发送： 帧头 地址 0x39 0x00 校验 帧尾
    D10 应 答 ： 帧 头 地 址 0x39 0x03 上 线 时 刻 个 数 上 线 时 刻 1
    上线时刻2 校验 帧尾
     */
    AskActiveOnline(0x39);

    companion object {
        fun findByCode(_code: Byte) = values().find { it.code == _code }
    }
}